package com.sitech.ibnms.c3p.command;

import com.sitech.ibnms.c3p.domain.C3pTaskNodeIo;
import com.sitech.ibnms.c3p.domain.C3pTaskNodeVars;
import com.sitech.ibnms.c3p.domain.ResourceAccount;
import com.sitech.ibnms.c3p.mappers.C3pTaskNodeIOMapper;
import com.sitech.ibnms.c3p.mappers.C3pTaskNodeVarsMapper;
import com.sitech.ibnms.c3p.task.notice.ExecutePolicy;
import com.sitech.ibnms.c3p.util.C3pConstants;
import com.sitech.ismp.workflow.domain.Task;
import com.sitech.ismp.workflow.util.DES3;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.*;

/**
 * Created by liujhc on 7/22.
 */
@Component
public class CommandParamsService {
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private C3pTaskNodeIOMapper nodeIOMapper;
    @Autowired
    private C3pTaskNodeVarsMapper nodeVarsMapper;

    public void saveParamsIn(String c3pTaskId, Task task, Set<Map.Entry<String, String>> inVars) {
        saveParams(c3pTaskId, task, inVars, null);
    }

    public void saveParamsOut(String c3pTaskId, Task task, Set<Map.Entry<String, String>> outVars) {
        saveParams(c3pTaskId, task, null, outVars);
    }

    public void saveParams(String c3pTaskId, Task task,
                           Set<Map.Entry<String, String>> inVars,
                           Set<Map.Entry<String, String>> outVars) {

        if (inVars == null) inVars = Collections.emptySet();
        if (outVars == null) outVars = Collections.emptySet();
        ArrayList<C3pTaskNodeIo> nodeIos = new ArrayList<C3pTaskNodeIo>(inVars.size() + outVars.size());
        /**
         * 如果参数值大于4000，把参数拆分成多条存储，每条2000个字符
         * 根据参数值的条数为paramType生成对应的序号， 格式为: OUT#01, OUT#02, ...
         * 不足最大位数的序号前面补零，以保证数据库排序逻辑正确
         */
        if (!inVars.isEmpty()) nodeIos.addAll(handleSaveParams(c3pTaskId, task, inVars, IOParamType.IN));
        if (!outVars.isEmpty()) nodeIos.addAll(handleSaveParams(c3pTaskId, task, outVars, IOParamType.OUT));
        if (!nodeIos.isEmpty()) nodeIOMapper.insertParam(nodeIos);
    }

    private List<C3pTaskNodeIo> handleSaveParams(String c3pTaskId, Task task, Set<Map.Entry<String, String>> paramSet, IOParamType paramType) {
        List<C3pTaskNodeIo> nodeIos = new ArrayList<C3pTaskNodeIo>();
        for (Map.Entry<String, String> entry : paramSet) {
            String key = entry.getKey();
            String value = entry.getValue();
            List<String> values;
            if (key.startsWith(C3pConstants.CMD_OUT_PREFIX)) {
                // 只有执行结果的输出才允许超过2000字符
                values = splitByLength(value, 2000);
            } else {
                values = Collections.singletonList(StringUtils.substring(value, 0, 2000));
            }
            int size = values.size();
            String indexFormat = "#%0" + String.valueOf(size).length() + "d";
            for (int i = 0; i < size; i++) {
                C3pTaskNodeIo io = new C3pTaskNodeIo();
                io.setC3pTaskId(c3pTaskId);
                io.setProcessId(task.getProcessId());
                io.setSerialNo(task.getSerialNo());
                io.setActivityId(task.getCurrActId());
                io.setTaskId(task.getTaskId());
                io.setParamName(key);
                io.setParamValue(values.get(i));
                if (size > 1) {
                    io.setParamType(paramType.name() + String.format(indexFormat, i));
                } else {
                    io.setParamType(paramType.name());
                }
                io.setDbTime(new Date());
                nodeIos.add(io);
            }
        }
        return nodeIos;
    }

    /**
     * @param str
     * @param length
     * @return
     */
    private List<String> splitByLength(String str, int length) {
        if (str == null) {
            return Collections.emptyList();
        }
        if (str.length() > length) {
            int size = (str.length() + length - 1) / length;
            ArrayList<String> strings = new ArrayList<String>(size);
            for (int i = 0; i < size; i++) {
                if (i + 1 < size) {
                    strings.add(i, str.substring(i * length, (i + 1) * length));
                } else {
                    strings.add(i, str.substring(i * length));
                    break;
                }
            }
            return strings;
        } else {
            return Collections.singletonList(str);
        }
    }

    public ExecutePolicy getExecutePolicy(Task task) {
        C3pTaskNodeIo nodeIo = nodeIOMapper.queryParamInByName(task.getSerialNo(), task.getCurrActId(), C3pConstants.PARAM_EXECUTE_POLICY);
        if (nodeIo != null) {
            return ExecutePolicy.valueOfOrNONE(nodeIo.getParamValue());
        }
        return ExecutePolicy.NONE;
    }

    /**
     * 解析环节的输入参数
     *
     * @param task
     * @param resourceAccount
     * @return
     */
    public Map<String, String> prepareParamsIn(Task task, ResourceAccount resourceAccount) {
        // 本环节输入参数配置
        List<C3pTaskNodeVars> cfgIn = nodeVarsMapper.queryParamsInConf(task.getProcessId(), task.getCurrActId());
        // 查询输出参数（如果环节有重名的输出参数会按时间取最新的）
        List<C3pTaskNodeIo> preOut = queryParamsOut(task.getSerialNo());

        // 解析输入配置，得到真实的输入参数
        Map<String, String> inVars = new HashMap<String, String>(cfgIn.size());

        if (!cfgIn.isEmpty()) {
            ScriptEngine engine = new ScriptEngineManager().getEngineByName("javascript");
            for (C3pTaskNodeIo param : preOut) {
                engine.put(param.getParamName(), param.getParamValue());
            }
            // 设置系统变量
            String targetIpAddress = resourceAccount.getIpAddr();
            String targetUnitId = resourceAccount.getUnitId();
            String targetUserName = resourceAccount.getUserName();
            String targetPassword = DES3.decrypt(resourceAccount.getPassword());
            engine.put("SYS.IP", targetIpAddress);
            engine.put("SYS.UNIT_ID", targetUnitId);
            engine.put("SYS.USERNAME", targetUserName);
            engine.put("SYS.PASSWORD", targetPassword);
            // 系统内置变量，在指令中可直接引用
            inVars.put("_TARGET_IP_ADDRESS", targetIpAddress);
            inVars.put("_TARGET_UNIT_ID", targetUnitId);
            inVars.put("_TARGET_USERNAME", targetUserName);
            inVars.put("_TARGET_PASSWORD", targetPassword);
            try {
                // 将带属性连接符"."的属性变为对象属性，从而可以直接引用，如{"user.name":"tom"}变为var user={name:"tom"}
                engine.eval("for (var key in this) {" +
                        "    if(key.indexOf('.')>0)(function (key) {" +
                        "        var p = '', arr = key.split('.'), script='';" +
                        "        for (var i = 0; i < arr.length; i++) {" +
                        "            var k = p + arr[i]; p = k + '.';" +
                        "            if(!this[k]) script += (k + '={};');" +
                        "        } " +
                        "        eval(script + key + '=' + com.sitech.ibnms.c3p.util.JSONUtil.toJSON(this[key]));" +
                        "    })(key);" +
                        "}");
            } catch (ScriptException e) {
                logger.warn("Setting C3pCommand system params error:", e);
            }
            for (C3pTaskNodeVars inParam : cfgIn) {
                try {
                    Object value = engine.eval(String.format("(%s)", inParam.getVarValue()));
                    logger.debug("C3pTaskNodeVars: {}={} --> {}", inParam.getVarName(), inParam.getVarValue(), value);
                    Assert.notNull(value, String.format("Parsing taskNode IN-PARAM return null: %s", inParam));
                    inVars.put(inParam.getVarName(), value.toString());
                } catch (ScriptException e) {
                    logger.error(String.format("Parsing taskNode IN-PARAM error:%s", inParam), e);
                }
            }
        }
        return inVars;
    }

    /**
     * @param task
     * @return varName:kpiId
     */
    public Map<String, String> prepareParamsOutConf(Task task) {
        // 本环节输出参数配置
        List<C3pTaskNodeVars> confOut = nodeVarsMapper.queryParamsOutConf(task.getProcessId(), task.getCurrActId());
        Map<String, String> outVars = new HashMap<String, String>(confOut.size());
        for (C3pTaskNodeVars out : confOut) {
            outVars.put(out.getVarName(), out.getKpiId());
        }
        return outVars;
    }

    public int getParamResourceCount(Task task) {
        return ObjectUtils.defaultIfNull(nodeIOMapper.queryIntegerByParamNameAndParamType(task.getTaskId(),
                C3pConstants.PARAM_RESOURCE_COUNT, IOParamType.IN), Integer.MIN_VALUE);
    }

    @Transactional
    public int updateCompleteResourceCount(Task task) {
        nodeIOMapper.updateCompleteResourceCount(task.getTaskId()
                , C3pConstants.PARAM_RESOURCE_COUNT_EXECUTED);
        return ObjectUtils.defaultIfNull(nodeIOMapper.queryIntegerByParamNameAndParamType(task.getTaskId(),
                C3pConstants.PARAM_RESOURCE_COUNT_EXECUTED, IOParamType.OUT),Integer.MIN_VALUE);
    }

    public int findNodeExecuteFailCount(Task task) {
        return nodeIOMapper.findNodeExecuteFailCount(task.getTaskId(), C3pConstants.CMD_OUT_EXEC_FLAG);
    }

    public List<C3pTaskNodeIo> queryParamsOut(int serialNo) {
        return nodeIOMapper.queryParamsOut(serialNo, C3pConstants.CMD_OUT_PREFIX);
    }

    public Collection<C3pTaskNodeIo> queryParamValuesRecursively(int rootSerialNo, String paramName, IOParamType paramType) {
        List<C3pTaskNodeIo> c3pTaskNodeIos = nodeIOMapper.queryParamValuesRecursively(rootSerialNo, paramName, paramType);
        Map<Object, C3pTaskNodeIo> groupedNodeIoMap = new LinkedHashMap<Object, C3pTaskNodeIo>();
        for (C3pTaskNodeIo nodeIo : c3pTaskNodeIos) {
            Object groupKey = Pair.of(nodeIo.getTaskId(), nodeIo.getParamName());
            C3pTaskNodeIo groupedNodeIo = groupedNodeIoMap.get(groupKey);
            if (groupedNodeIo == null) {
                groupedNodeIoMap.put(groupKey, nodeIo);
            } else {
                groupedNodeIo.setParamValue(groupedNodeIo.getParamValue() + nodeIo.getParamValue());
            }
        }
        return groupedNodeIoMap.values();
    }
}
